﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace UlsStudio.Engine.SqlParsers
{
    /// <summary>
    /// Parses SQL select statements
    /// </summary>
    public class SimpleSqlSelectStatementInfo
    {
        #region Privates, Constants, Constructors

        private string _sqlOriginal = string.Empty;
        private List<string> _fields = null;
        private bool _isDistinct = false;

        const string WORD_SELECT = "select ";
        const string WORD_DISTINCT = "distinct ";
        const string WORD_ORDER = "order ";
        const string WORD_BY = "by ";
        const string WORD_WHERE = "where ";
        const string WORD_FROM = " from ";

        internal SimpleSqlSelectStatementInfo()
        {
            // Init vars
            this.WhereClauseString = string.Empty;
            this.OrderByClauseString = string.Empty;
            _fields = new List<string>();
            _whereClauses = new WhereClauseList();
        }
        public SimpleSqlSelectStatementInfo(string sql)
            : this()
        {
            // This starts a parse
            this.OriginalSql = sql;
        }

        /// <summary>
        /// Create a new statement object based on another statement
        /// </summary>
        /// <param name="copy">Statement to copy</param>
        public SimpleSqlSelectStatementInfo(SimpleSqlSelectStatementInfo copy) : this ()
        {
            if (copy == null)
            {
                throw new ArgumentNullException("copy");
            }
            // TODO: Complete member initialization
            this.OriginalSql = copy.ToString();
        }

        #endregion
        
        #region Properties

        public string OriginalSql
        {
            get { return _sqlOriginal; }
            set { _sqlOriginal = value; ParseStatement(); }
        }

        /// <summary>
        /// Collection of fields as a list
        /// </summary>
        public List<string> FieldCollection
        {
            get { return _fields; }
            set { _fields = value; }
        }

        public bool HaveWhereClause { get; set; }

        /// <summary>
        /// Query has "distinct" qualifier
        /// </summary>
        public bool IsDistinct
        {
            get { return _isDistinct; }
            set { _isDistinct = value; }
        }

        /// <summary>
        /// String representation of the fields collection
        /// </summary>
        public virtual string FieldString
        {
            get
            {
                return GetFieldStringForFieldCollection(this.FieldCollection);
            }
            set
            {
                _fields.Clear();

                string[] fieldTokens = value.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                foreach (string f in fieldTokens)
                {
                    _fields.Add(f.TrimEnd(",".ToCharArray()));
                }
            }
        }

        public string TableName { get; set; }

        private string WhereClauseString { get; set; }

        private WhereClauseList _whereClauses = null;

        /// <summary>
        /// Where clauses as a WhereClauseList
        /// </summary>
        public WhereClauseList WhereClauses
        {
            get { return _whereClauses; }
        }

        public string OrderByClauseString { get; set; }

        /// <summary>
        /// Statement is valid. Set by ParseStatement()
        /// </summary>
        public bool StatmentIsValid { get; set; }

        #endregion

        #region String Processing Methods

        /// <summary>
        /// Work out main sections of statement
        /// </summary>
        private void ParseStatement()
        {
            // Fail by default
            this.StatmentIsValid = false;

            // Sanity & clean-up
            if (string.IsNullOrEmpty(this.OriginalSql))
                return;
            this._sqlOriginal = OriginalSql.Trim();

            // Find keyword locations
            int selectWordIdx = this.OriginalSql.IndexOf(WORD_SELECT, StringComparison.CurrentCultureIgnoreCase);
            int distinctWordIdx = this.OriginalSql.IndexOf(WORD_DISTINCT, StringComparison.CurrentCultureIgnoreCase);
            int fromWordIdx = this.OriginalSql.IndexOf(WORD_FROM, StringComparison.CurrentCultureIgnoreCase);
            int whereWordIdx = this.OriginalSql.IndexOf(WORD_WHERE, StringComparison.CurrentCultureIgnoreCase);

            int orderWordIdx = this.OriginalSql.IndexOf(WORD_ORDER, StringComparison.CurrentCultureIgnoreCase);
            int byWordIdx = this.OriginalSql.IndexOf(WORD_BY, StringComparison.CurrentCultureIgnoreCase);

            // Is there a SELECT clause?
            if (selectWordIdx == -1) return;

            int startOfFieldsIndex = 0;

            // Is there a DISTINCT clause?
            if (distinctWordIdx == -1)
            {
                this.IsDistinct = false;
                startOfFieldsIndex = selectWordIdx + WORD_SELECT.Length;
            }
            else
            {
                this.IsDistinct = true;
                startOfFieldsIndex = distinctWordIdx + WORD_DISTINCT.Length;
            }

            // check for content after SELECT
            if (this.OriginalSql.Substring(startOfFieldsIndex).Length < 3)
                return;

            // Is there a FROM clause?
            if (fromWordIdx == -1)
                return;

            // Are there any fields mentioned?
            if (startOfFieldsIndex >= fromWordIdx)
                return;

            // Get fields wanted 
            this.FieldString = this.OriginalSql.Substring(startOfFieldsIndex, fromWordIdx - startOfFieldsIndex).Trim();

            // Get table name
            int fromClauseStart = fromWordIdx + WORD_FROM.Length;

            // Get table name
            int nextSpaceAfterFromClause = this.OriginalSql.IndexOf(" ", fromClauseStart + 1);
            int nextCRLFAfterFromClause = this.OriginalSql.IndexOf("\n", fromClauseStart + 1);
            
            // Does a CRLF seperate table-name & WHERE clause?
            int endOfTableName = nextSpaceAfterFromClause;
            if (nextCRLFAfterFromClause > -1 && nextCRLFAfterFromClause < nextSpaceAfterFromClause)
            {
                endOfTableName = nextCRLFAfterFromClause;
            }
            int finishedTableIdx = 0;
            if (nextSpaceAfterFromClause == -1)
                finishedTableIdx = this.OriginalSql.Length - fromClauseStart;
            else
                finishedTableIdx = endOfTableName - fromClauseStart;
            this.TableName = this.OriginalSql.Substring(fromClauseStart, finishedTableIdx).Trim();

            // Check table-name
            if (!string.IsNullOrEmpty(this.TableName) && this.TableName != Constants.TABLE_NAME_LOGDATA)
            {
                return;
            }

            // No WHERE clauses just fields?
            if (whereWordIdx == -1 && orderWordIdx == -1)
            {
                // No clauses, just a select
                this.StatmentIsValid = true;
                return;
            }

            // Is there a where clause?
            if (whereWordIdx > -1)
            {
                int whereClauseStart = whereWordIdx + WORD_WHERE.Length;

                // check for content after where
                if (this.OriginalSql.Substring(whereWordIdx + WORD_WHERE.Length).Length < 1)
                    return;

                if (orderWordIdx > -1)
                {
                    // If there's an order by clause, take upto that
                    this.WhereClauseString = this.OriginalSql.Substring(whereClauseStart, orderWordIdx - whereClauseStart);
                    this.HaveWhereClause = true;
                }
                else
                {
                    // If there's no order by clause, take rest of string
                    this.WhereClauseString = this.OriginalSql.Substring(whereClauseStart, this.OriginalSql.Length - whereClauseStart);

                    // Count number of brackets match ( & )
                    bool bracketAreBalanced = this.WhereClauseString.Split("(".ToCharArray()).Length == this.WhereClauseString.Split(")".ToCharArray()).Length;
                    if (!bracketAreBalanced) return;

                    try
                    {
                        // Try and get the clauses to check the statement's valid
                        _whereClauses = GetWhereClauses(this.WhereClauseString);
                    }
                    catch (ArgumentOutOfRangeException)
                    {
                        return;
                    }

                    this.StatmentIsValid = true;
                    this.HaveWhereClause = true;
                    return;
                }
            }
            else
            {
                this.HaveWhereClause = false;
            }

            // Grab any order clause
            if (orderWordIdx > -1)
            {
                if (byWordIdx == -1)
                    return;

                this.TableName = this.OriginalSql.Substring(fromClauseStart, orderWordIdx - fromClauseStart).Trim();

                // Check for "BY"
                if (this.OriginalSql.Substring(byWordIdx + WORD_BY.Length).Length < 1)
                {
                    return;
                }
                int byClauseStart = byWordIdx + WORD_BY.Length;

                // Get clause
                this.OrderByClauseString = this.OriginalSql.Substring(byClauseStart);

                this.StatmentIsValid = true;
            }
        }

        /// <summary>
        /// Finds either AND/OR in a string, whichever comes first
        /// </summary>
        private WhereOperatorSearchInfo FindNextWhereOperator(string text, int searchStart)
        {
            const string OP_OR = " or ", OP_AND = " and ";
            int orLocation = text.ToLower().IndexOf(OP_OR, searchStart);
            int andLocation = text.ToLower().IndexOf(OP_AND, searchStart);
            bool haveOr = (orLocation != -1), haveAnd = (andLocation != -1);

            // Figure out if and/or comes first
            bool clauseIsAnd = false;
            int thisClauseEndPoint = 0, clauseLength = 0;
            bool useOr = false;
            if (haveOr & haveAnd & orLocation < andLocation) useOr = true;
            else if (haveOr & haveAnd & orLocation > andLocation) useOr = false;
            else if (!haveAnd) useOr = true;
            else if (!haveOr) useOr = false;

            // Get location of next clause operator
            if (useOr)
            {
                thisClauseEndPoint = orLocation;
                clauseLength = OP_OR.Length;
            }
            else
            {
                thisClauseEndPoint = andLocation;
                clauseLength = OP_AND.Length;
                clauseIsAnd = true;
            }

            return new WhereOperatorSearchInfo() { OperatorStart = thisClauseEndPoint, OpStringLength = clauseLength, OperateIsAnd = clauseIsAnd };
        }

        #region Private Classes
        /// <summary>
        /// Represents the next string location of a where clause operator
        /// </summary>
        private class WhereOperatorSearchInfo
        {
            public int OperatorStart { get; set; }
            public int OpStringLength { get; set; }
            public int OpEnd { get { return this.OperatorStart + this.OpStringLength; } }
            public bool OperateIsAnd { get; set; }
            public bool OperatorWasFound { get { return this.OperatorStart > -1; } }
        }

        #endregion

        /// <summary>
        /// Counts how many chars there are of a certain type in a string from a certain point
        /// </summary>
        private List<int> GetDirectionalCharCount(string fullString, int fromWhere, char wantedChar, bool lookLeft)
        {
            if (fullString == null) throw new ArgumentNullException("fullString");
            if (fromWhere > fullString.Length - 1 || fromWhere < 0) throw new ArgumentOutOfRangeException("fromWhere");

            List<int> charLocations = new List<int>();
            int counterAddition = 0, countToValue = 0, currentValue = fromWhere;        // Start looking from set location
            if (lookLeft)
            {
                counterAddition = -1;
                countToValue = 0;
            }
            else
            {
                countToValue = fullString.Length;
                counterAddition = +1;
            }

            while (currentValue != countToValue)
            {

                // Compare char
                if (fullString[currentValue] == wantedChar)
                {
                    charLocations.Add(currentValue);
                }

                // Shift char possition
                currentValue = currentValue + counterAddition;
            }

            return charLocations;
        }

        /// <summary>
        /// Breaks where clauses string into clause-object tree
        /// </summary>
        private WhereClauseList GetWhereClauses(string text)
        {
            if (string.IsNullOrEmpty(text)) throw new ArgumentOutOfRangeException("No SQL to process");
            List<int> openingBrackets = GetDirectionalCharCount(text, 0, "(".ToCharArray()[0], false);
            List<int> closingBrackets = GetDirectionalCharCount(text, 0, ")".ToCharArray()[0], false);

            if (openingBrackets.Count != closingBrackets.Count)
            {
                throw new ArgumentOutOfRangeException("Inbalanced bracket count");
            }

            WhereClauseList clauses = new WhereClauseList();
            bool keepLooking = true; int searchStartPoint = 0, rightSideSearchStart = text.Length;
            while (keepLooking)
            {
                // Look for nested clausen first as they may have AND/ORs, but are their own single clause
                int nestedWhereStartLocation = text.ToLower().IndexOf("(", searchStartPoint);
                int nestedWhereEndLocation = -1;
                if (nestedWhereStartLocation > -1) nestedWhereEndLocation = text.ToLower().IndexOf(")", nestedWhereStartLocation);

                bool haveNestedWhereClause = nestedWhereEndLocation > -1;

                // Get next where clause
                WhereOperatorSearchInfo nextOperatorInf = FindNextWhereOperator(text, searchStartPoint);

                // Are there any more clauses?
                bool foundMoreClauseOperators = nextOperatorInf.OperatorStart > -1;
                if (foundMoreClauseOperators)
                {
                    // Is the next clause part of a nested clause?
                    bool nextOperatorIsPartOfNestedWhereClause = (nestedWhereStartLocation != -1 && nextOperatorInf.OperatorStart > nestedWhereStartLocation);

                    if (nextOperatorIsPartOfNestedWhereClause)
                    {
                        nestedWhereStartLocation++;     // Excluse the opening bracket from substring

                        // Grab clause upto first closing bracket
                        string thisSegmentUptoCloseBracket = text.Substring(nestedWhereStartLocation, nestedWhereEndLocation - nestedWhereStartLocation).Trim();

                        // How many sub-nested-clauses are there in this nested clause? This will determine how many more closing brackets we need.
                        List<int> openingBracketsInNestedClause = GetDirectionalCharCount(thisSegmentUptoCloseBracket, 0, "(".ToCharArray()[0], false);
                        int closingBracketLocation = nestedWhereEndLocation;
                        for (int i = 0; i < openingBracketsInNestedClause.Count; i++)
                        {
                            // Look for next closing bracket after the last one
                            closingBracketLocation = text.ToLower().IndexOf(")", closingBracketLocation + 1);
                        }
                        string theProperSubClause = text.Substring(nestedWhereStartLocation, closingBracketLocation - nestedWhereStartLocation).Trim();


                        NestedWhereClause thisNestedClause = new NestedWhereClause(theProperSubClause);
                        WhereOperatorSearchInfo operatorAfterThisNestedClause = FindNextWhereOperator(text, nestedWhereEndLocation);

                        // Find next clause location after this nested clause
                        WhereClauseList nestedClauses = GetWhereClauses(theProperSubClause);
                        foreach (WhereClause childClause in nestedClauses.Items)
                        {
                            childClause.Parent = thisNestedClause;
                        }
                        thisNestedClause.Children = nestedClauses;

                        // Push cursor past last bracket if there's more to process, and past next clause operator
                        if (operatorAfterThisNestedClause.OperatorWasFound)
                        {
                            searchStartPoint = operatorAfterThisNestedClause.OperatorStart + operatorAfterThisNestedClause.OpStringLength;
                        }
                        else
                        {
                            // We're done, no more clauses to look for in this text
                            keepLooking = false;
                        }

                        // Determin type of proceding operator
                        if (operatorAfterThisNestedClause.OperatorWasFound)
                        {
                            if (operatorAfterThisNestedClause.OperateIsAnd)
                                thisNestedClause.FollowingOperator = FollowingOperatorType.And;
                            else
                                thisNestedClause.FollowingOperator = FollowingOperatorType.Or;

                        }
                        clauses.Add(thisNestedClause);
                    }
                    else        // foundMoreClauseOperators==false
                    {
                        // There are more clauses, take just this one
                        int endOfClause = nextOperatorInf.OperatorStart + nextOperatorInf.OpStringLength;
                        string thisSegment = text.Substring(searchStartPoint, nextOperatorInf.OperatorStart - searchStartPoint).Trim();

                        WhereClause thisWhereClause = new WhereClause(thisSegment);
                        if (nextOperatorInf.OperateIsAnd) thisWhereClause.FollowingOperator = FollowingOperatorType.And;
                        else thisWhereClause.FollowingOperator = FollowingOperatorType.Or;
                        try
                        {
                            clauses.Add(thisWhereClause);
                        }
                        catch (ArgumentOutOfRangeException)
                        {
                            // A bad where clause, fail whole parse
                            throw;
                        }

                        // Set scan-start point as end of this segment
                        searchStartPoint = endOfClause;
                    }
                }
                else
                {
                    string lastSegment = text.Substring(searchStartPoint).Trim();

                    keepLooking = false;        // No more clauses, we are done
                    clauses.Add(new WhereClause(lastSegment));
                }
            }
            return clauses;
        }

        /// <summary>
        /// Turn a field-collection into a comma-seperated string like 'field1, field2, f3'
        /// </summary>
        internal static string GetFieldStringForFieldCollection(List<string> fields)
        {
            string fieldsString = string.Empty;
            const string SEP = ", ";

            // Build a field-list
            foreach (string field in fields)
            {
                string formattedField = field;

                fieldsString += string.Format("{0}{1}", formattedField, SEP);
            }
            fieldsString = fieldsString.TrimEnd(SEP.ToCharArray());

            return fieldsString;
        }

        #endregion

        #region Overrides

        /// <summary>
        /// Write this command out in plain text with optional line-breaks.
        /// </summary>
        /// <returns></returns>
        public string ToString(bool lineBreaks)
        {
            // Build SELECT
            string selectCommand = "select";
            if (this.IsDistinct)
            {
                selectCommand = "select distinct";
            }
            string statement = string.Format("{2} {0} from {1} ", this.FieldString, this.TableName, selectCommand);     // Space at the end is intentional

            // Add clauses
            if (this.WhereClauses.Items.Count > 0)
            {
                if (lineBreaks)
                    statement += string.Format("\n  where {0} ", this.WhereClauses.ToString());
                else
                    statement += string.Format("where {0} ", this.WhereClauses.ToString());
            }
            if (!string.IsNullOrEmpty(this.OrderByClauseString))
            {
                statement += string.Format("order by {0} ", this.OrderByClauseString);
            }
            return statement.Trim();
        }

        /// <summary>
        /// Returns a plain-text version of this SELECT statement, with line breaks
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return this.ToString(false);
        }

        #endregion

        /// <summary>
        /// Make a copy of this'un
        /// </summary>
        public SimpleSqlSelectStatementInfo Clone()
        {
            SimpleSqlSelectStatementInfo clone = new SimpleSqlSelectStatementInfo(this.ToString());
            //clone.FieldCollection = new List<string>(this.FieldCollection);
            //clone.TableName = this.TableName;
            //clone.OrderByClauseString = this.OrderByClauseString;
            //clone.WhereClauseString = this.WhereClauseString;

            return clone;
        }

        #region Static
        public static SimpleSqlSelectStatementInfo BasicIdOnlyQuery
        {
            get
            {
                return new SimpleSqlSelectStatementInfo(string.Format("select {0} from {1}", Constants.FIELD_NAME_ID, Constants.TABLE_NAME_LOGDATA));
            }
        }
        #endregion
    }

    #region RowLimitSqlSelectStatement Class

    /// <summary>
    /// A SELECT statement for used on range-selects
    /// </summary>
    public class RowLimitSqlSelectStatement : SimpleSqlSelectStatementInfo
    {
        public SimpleSqlSelectStatementInfo ParentFullQuery { get; set; }
        public int FromRow { get; set; }
        public int ToRow { get; set; }

        public RowLimitSqlSelectStatement(SimpleSqlSelectStatementInfo parentFullQuery, int fromRowID, int toRowID)
            : base()
        {
            // Sanity
            if (parentFullQuery == null) 
                throw new ArgumentNullException("parentFullQuery");
            if (!parentFullQuery.StatmentIsValid) 
                throw new ArgumentOutOfRangeException("parentFullQuery", "A valid query is needed");

            if (fromRowID < 0) throw new ArgumentOutOfRangeException("fromRowID", "From value needs to be at least 0");

            // Local vars
            this.ParentFullQuery = parentFullQuery;
            this.FromRow = fromRowID;
            this.ToRow = toRowID;

            // This is always valid
            this.StatmentIsValid = true;
        }

        /// <summary>
        /// This query returns whatever the nested query returns
        /// </summary>
        public override string FieldString
        {
            get { return this.ParentFullQuery.FieldString; }
            set { base.FieldString = value; }
        }

        public override string ToString()
        {
            // We need to remove ordering from parent query & do it in this query instead
            SimpleSqlSelectStatementInfo queryCopyWithoutOrdering = this.ParentFullQuery.Clone();
            queryCopyWithoutOrdering.OrderByClauseString = string.Empty;

            // Build SELECT based on child select

            string statement = string.Format("SELECT {0} FROM ({1}) LIMIT {3} OFFSET {2} ",
                this.FieldString,
                this.ParentFullQuery.ToString(),
                this.FromRow,
                this.ToRow - this.FromRow);

            // Add clauses (any WHERE clauses are done in embedded query)
            if (!string.IsNullOrEmpty(this.OrderByClauseString))
            {
                statement += string.Format("ORDER BY {0} ", this.ParentFullQuery.OrderByClauseString);
            }
            return statement.Trim();
        }
    }
    #endregion

    #region Where Clause Classes
    public class NestedWhereClause : WhereClause
    {
        public NestedWhereClause() : this(string.Empty) { }
        public NestedWhereClause(string text) : base(text) { }
        public override string Text
        {
            get { return string.Format("({0})", base.Text); }
            set { base.Text = value; }
        }
    }
    public class WhereClause
    {
        public WhereClause(string text)
        {
            if (text == null) throw new ArgumentOutOfRangeException("No text");
            this.Children = new WhereClauseList();
            this.Text = text;
            this.ID = Guid.NewGuid();
            this.FollowingOperator = FollowingOperatorType.None;
        }

        #region Properties

        public Guid ID { get; set; }
        public WhereClauseList Children { get; set; }
        public virtual string Text { get; set; }
        public FollowingOperatorType FollowingOperator { get; set; }
        #endregion

        public NestedWhereClause Parent { get; set; }
    }
    public class WhereClauseList
    {
        public List<WhereClause> Items { get; set; }
        public WhereClauseList()
        {
            this.Items = new List<WhereClause>();
        }


        public void Add(WhereClause c)
        {
            this.Items.Add(c);
        }
        public void Add(WhereClause c, FollowingOperatorType setPrecedingClauseOperator)
        {
            if (this.Items.Count > 0)
            {
                this.Items[this.Items.Count - 1].FollowingOperator = setPrecedingClauseOperator;
            }
            this.Items.Add(c);
        }


        /// <summary>
        /// Find a where clause in the current query by tag-name
        /// </summary>
        public WhereClause FindSearchClauseByText(string text)
        {
            return FindSearchClauseByText(text, this);
        }
        private static WhereClause FindSearchClauseByText(string text, WhereClauseList whereClauseList)
        {
            // Brackets test
            if (whereClauseList.Items.Count == 1)
            {
                if (string.Format("({0})", text) == whereClauseList.Items[0].Text)
                {
                    return whereClauseList.Items[0];
                }
            }
            foreach (WhereClause c in whereClauseList.Items)
            {
                if (c.Text == text) return c;
                else
                {
                    if (c.Children.Items.Count > 0)
                    {
                        WhereClause wantedClause = FindSearchClauseByText(text, c.Children);
                        if (wantedClause != null) return wantedClause;
                    }
                }
            }

            return null;
        }

        public override string ToString()
        {
            string s = string.Empty;

            int iterationCount = 0;
            foreach (WhereClause whereClause in this.Items)
            {
                if (whereClause is NestedWhereClause)
                    s += string.Format("({0})", whereClause.Children.ToString());
                else
                    s += string.Format(whereClause.Text);


                // Add following clause operator, if there is one & we're not the last item in the list
                if (iterationCount < this.Items.Count - 1)
                {
                    if (whereClause.FollowingOperator == FollowingOperatorType.And)
                    {
                        s += " and ";
                    }
                    else if (whereClause.FollowingOperator == FollowingOperatorType.Or)
                    {
                        s += " or ";
                    }
                }
                iterationCount++;
            }
            return s;
        }


        public void RemoveByText(string tag)
        {
            RemoveByText(tag, this);
        }
        private void RemoveByText(string text, WhereClauseList clauses)
        {
            List<WhereClause> removeList = new List<WhereClause>();
            foreach (WhereClause wc in clauses.Items)
            {
                Logging.Writeline("Analysing {0}", wc.Text);
                if (wc.Text == text || string.Format("({0})", text) == wc.Text)
                {
                    removeList.Add(wc);
                    Logging.Writeline("Removing {0}", wc.Text);
                }

                if (wc.Children.Items.Count > 0)
                {
                    Logging.Writeline("Analysing {0} children...", wc.Text);
                    RemoveByText(text, wc.Children);

                    // Did that kill it?
                    if (wc.Children.Items.Count == 0)
                    {
                        Logging.Writeline("Parent no longer needed...", wc.Text);
                        removeList.Add(wc);
                    }
                }


            }

            // Cleanup parent nested clauses now empty
            foreach (WhereClause deleteClause in removeList)
            {
                clauses.Items.Remove(deleteClause);
            }
        }

        /// <summary>
        /// Removes all clauses
        /// </summary>
        public void Clear()
        {
            this.Items.Clear();
        }
    }

    public enum FollowingOperatorType
    {
        None,
        And,
        Or
    }
    #endregion

}